#include <ap/settings/ds_settings.h>

#include "ap/ap_comm.h"

#include "m3d.h"

#include "ds/ds_algorithm.h"
#include "ds/ds_vector.h"
#include "ds/ds_lf_map.h"
#include "ds/ds_pool.h"
#include "ds/ds_map.h"
#include "ds/ds_param.h"
#include "ds/ds_event_mgr.h"

dsEVENT_MGR *gEventMgr = NULL;
dsSTATE_MGR *gStateMgr = NULL;

const dsSTRID valId  = dsSTRID("value", FALSE);

/*************************************************************
* dsInitEventMgr()
* 
*************************************************************/
BOOL dsInitEventMgr(void)
{
   gEventMgr = apNEW dsEVENT_MGR;
   gEventMgr->InitThread();

   gStateMgr = apNEW dsSTATE_MGR;
   gStateMgr->InitThread();

   return TRUE;
}

/*************************************************************
* dsTermEventMgr()
* 
*************************************************************/
void dsTermEventMgr(void)
{
   gEventMgr->TermThread();
   
   delete gEventMgr;
   gEventMgr = NULL;

   gStateMgr->TermThread();

   delete gStateMgr;
   gStateMgr = NULL;

   return;
}

/*************************************************************
* dsUpdateEventMgr()
* 
*************************************************************/
void dsUpdateEventMgr(void)
{
   gEventMgr->UpdateThread();
   gStateMgr->UpdateThread();
   
   return;
}

/*************************************************************
* dsEVENT_MGR::dsEVENT_MGR()
* 
*************************************************************/
dsEVENT_MGR::dsEVENT_MGR(void) : subscriberMap(AP_CL), itemMap(AP_CL), items(AP_CL), handlers(AP_CL), events(AP_CL), subscribers(AP_CL)
{
   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      THREAD_INFO &ti = threadInfo[i];

      ti.idxEventEmpty     = -1;
      ti.idxEventHead      = -1;
      ti.idxEventTail      = -1;
      ti.isActive          = FALSE;
      ti.numEmptyElements  = 0;

      for (int j = 0; j < DS_EVENT_MAX_THREAD; j++) {
         ti.idxThreadEvent[j] = -1;
      }
   }
   itemMap.Init(1024);
   return;
}

/*************************************************************
* dsEVENT_MGR::InitThread()
* 
*************************************************************/
void dsEVENT_MGR::InitThread(void)
{
   osLOCKER locker(lock);

   BOOL isAlreadyInited = FALSE;
   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      THREAD_INFO &ti = threadInfo[i];

      if (ti.isActive == TRUE && GetThreadId() == i) {
         isAlreadyInited = TRUE;
         break;
      }
   }

   ASSERT(!isAlreadyInited);
   if (isAlreadyInited) {
      return;
   }

   ITEM_MAP::RegisterThread();

   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      THREAD_INFO &ti = threadInfo[i];

      if (ti.isActive == FALSE) {
         GetThreadId() = i;

         int idx = -1;
         for (int j = 0; j < DS_EMPTY_CHAIN_LENGTH; j++) {
            int next = idx;
            idx = events.Alloc();
            events[idx].idxNext = next;
         }

         if (ti.idxEventHead < 0) {
            int idxCur = events.Alloc();
            EVENT &ev = events[idxCur];
            ev.idxNext     = -1;
            ev.id          = -1;
            ev.age         = 0;
            ev.threadMask  = 0;
            for (int j = 0; j < DS_EVENT_MAX_THREAD; j++) {
               ev.refCounters[j] = -1;
            }

            osMemoryBarrier();

            ti.idxEventTail      = idxCur;
            ti.idxEventHead      = idxCur;
         } else {
            ASSERT(ti.idxEventHead == ti.idxEventTail); // head element is already allocated
         }

         ti.idxEventEmpty     = idx;
         ti.numEmptyElements  = DS_EMPTY_CHAIN_LENGTH;
         ti.isDispatching     = FALSE;
         ti.age               = 0;

         for (int j = 0; j < DS_EVENT_MAX_THREAD; j++) {
            ti.idxThreadEvent[j] = -1;
         }

         osMemoryBarrier();

         ti.isActive          = TRUE;

         return;
      }
   }

   STRONG_ASSERT(FALSE); // too many thread registered

   return;
}

/*************************************************************
* dsEVENT_MGR::TermThread()
* 
*************************************************************/
void dsEVENT_MGR::TermThread(void)
{
   int threadId = GetThreadId();
   ASSERT(threadId >= 0 && threadId < DS_EVENT_MAX_THREAD);

   if (threadId < 0 || threadId >= DS_EVENT_MAX_THREAD) {
      return;
   }

   PurgeEvents();

   ITEM_MAP::UnregisterThread();

   THREAD_INFO &ti = threadInfo[threadId];

   // wait until all pending events are processed
   do {
      DiscardProcessedEvents();
      osSleep(5);
   } while (ti.idxEventTail != ti.idxEventHead); 

   osLOCKER locker(lock);

   // erase empty chain
   int idx = ti.idxEventEmpty;
   while (idx >= 0) {
      int idxErase = idx;
      idx = events[idx].idxNext;
      events.Free(idxErase);
   }
   ti.idxEventEmpty     = -1;
   ti.numEmptyElements  = 0;

   // now we erase any mention of this thread in the system

   ITEM_MAP::const_iterator it = itemMap.Begin();
   while (!it.isEnd()) {
      ITEM_REC &ir = items[it.Get()];

      int idxHandler = ir.threadInfo[threadId].idxHandlerChain;
      ir.threadInfo[threadId].idxHandlerChain = -1; // not any more
      while (idxHandler >= 0) {
         ITEM_HANDLER &handler = handlers[idxHandler];

         if (handler.type == ST_POLL) { // poll handler. for these we should also erase subscriber records
            SUBSCRIBER_REC &sr = subscribers[handler.idxSubscriber];

            if (--sr.refCnt == 0) {

               // let's erase all unreceived notifications
               int idxNotify = sr.idxNotifyHead;
               while (idxNotify >= 0) {
                  PENDING_ITEM &pi = ti.notifications[idxNotify];
                  // it's ok to do this w/o memory barrier, because no one is going to miss this data
                  events[pi.idxEvent].refCounters[threadId]--; // mark event as received
                  int next = pi.idxNext;
                  ti.notifications.Free(idxNotify);
                  idxNotify = next;
               }

               subscribers.Free(handler.idxSubscriber);
            }
         }

         int next = handler.idxNext;
         handlers.Free(idxHandler);
         idxHandler = next;
      }

      it++;
   }

   osMemoryBarrier(); // let's ensure all data is submitted to memory before we...

   ti.isActive = FALSE; // ...mark thread as not active, so other threads may allocate it
   GetThreadId() = -1;

   return;
}

/*************************************************************
* dsEVENT_MGR::UpdateThread()
* 
*************************************************************/
void dsEVENT_MGR::UpdateThread(void)
{
   ITEM_MAP::UpdateThread();

   return;
}

/*************************************************************
* dsEVENT_MGR::RegisterEvent()
* 
*************************************************************/
int dsEVENT_MGR::RegisterEvent(dsSTRID name)
{
   const int *item = itemMap.Find(name.CStr());
   if (item != NULL) {
      return *item;
   }

   osLOCKER locker(lock);

   item = itemMap.Find(name.CStr()); // search once again to ensure no one added this item while we were acquiring the lock
   if (item != NULL) {
      return *item;
   }

   int newItemId = items.Alloc();
   ITEM_REC &rec = items[newItemId];
   rec.name    = dsSTRID(name, false);
   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      rec.threadInfo[i].idxHandlerChain = -1;
   }

   itemMap.Insert(rec.name.CStr(), newItemId); // memory barrier is inside, so the data is immediately available for other threads

   return newItemId;
}

/*************************************************************
* dsEVENT_MGR::SubscribeEvent()
* 
*************************************************************/
void dsEVENT_MGR::SubscribeEvent(int id, dsEVENT_HANDLER *pHandler, DWORD userId)
{
   ITEM_REC &rec = items[id];

   THREAD_INFO &ti = threadInfo[GetThreadId()];
   THREAD_ITEM_INFO &info = rec.threadInfo[GetThreadId()];

   int idx = info.idxHandlerChain;
   while (idx >= 0) {
      ITEM_HANDLER &handler = handlers[idx];
      if (handler.type == ST_IFACE && handler.userId == userId && handler.pHandler == pHandler) {
         return; // already subscribed
      }
      idx = handler.idxNext;
   }

   osLOCKER locker(lock);

   int idxNewHandler = handlers.Alloc();
   ITEM_HANDLER &handler = handlers[idxNewHandler];
   handler.type      = ST_IFACE;
   handler.pHandler  = pHandler;
   handler.userId    = userId;
   handler.idxNext   = info.idxHandlerChain;
   handler.age       = ti.age;

   info.idxHandlerChain = idxNewHandler;

   return;
}

/*************************************************************
* dsEVENT_MGR::SubscribeEvent()
* 
*************************************************************/
void dsEVENT_MGR::SubscribeEvent(int id, CBHandleEvent cbHandler, DWORD userId)
{
   ITEM_REC &rec = items[id];

   THREAD_INFO &ti = threadInfo[GetThreadId()];
   THREAD_ITEM_INFO &info = rec.threadInfo[GetThreadId()];

   int idx = info.idxHandlerChain;
   while (idx >= 0) {
      ITEM_HANDLER &handler = handlers[idx];
      if (handler.type == ST_CALLBACK && handler.userId == userId && handler.cbHandler == cbHandler) {
         return; // already subscribed
      }
      idx = handler.idxNext;
   }

   osLOCKER locker(lock);

   int idxNewHandler = handlers.Alloc();
   ITEM_HANDLER &handler = handlers[idxNewHandler];
   handler.type      = ST_CALLBACK;
   handler.cbHandler = cbHandler;
   handler.userId    = userId;
   handler.idxNext   = info.idxHandlerChain;
   handler.age       = ti.age;

   info.idxHandlerChain = idxNewHandler;

   return;
}

/*************************************************************
* dsEVENT_MGR::SubscribeEvent()
* 
*************************************************************/
int dsEVENT_MGR::SubscribeEvent(int id, const void *subscriber, DWORD userId)
{
   ITEM_REC &rec = items[id];

   THREAD_INFO &ti = threadInfo[GetThreadId()];
   THREAD_ITEM_INFO &info = rec.threadInfo[GetThreadId()];

   int idx = info.idxHandlerChain;
   while (idx >= 0) {
      ITEM_HANDLER &handler = handlers[idx];
      if (handler.type == ST_POLL && handler.userId == userId && subscribers[handler.idxSubscriber].subscriber == subscriber) {
         return handler.idxSubscriber; // already subscribed
      }
      idx = handler.idxNext;
   }

   osLOCKER locker(lock);

   SUBSCRIBER_MAP::const_iterator it = subscriberMap.Find(subscriber);
   int idxSub;
   if (it.IsDone()) {
      idxSub = subscribers.Alloc();
      SUBSCRIBER_REC &sr = subscribers[idxSub];
      sr.idxNotifyHead  = -1;
      sr.idxNotifyTail  = -1;
      sr.refCnt         = 1;
      sr.subscriber     = subscriber;
#ifndef _RETAIL
      sr.threadId       = GetThreadId();
#endif

      subscriberMap.Insert(subscriber, idxSub);
   } else {
      idxSub = *it.get();
#ifndef _RETAIL
      STRONG_ASSERT(subscribers[idxSub].threadId == GetThreadId()); // this subscriber is already registered in a different thread
#endif
      subscribers[idxSub].refCnt ++;
   }

   int idxNewHandler = handlers.Alloc();
   ITEM_HANDLER &handler = handlers[idxNewHandler];
   handler.type            = ST_POLL;
   handler.idxSubscriber   = idxSub;
   handler.userId          = userId;
   handler.idxNext         = info.idxHandlerChain;
   handler.age             = ti.age;

   info.idxHandlerChain = idxNewHandler;

   return idxSub;
}

/*************************************************************
* dsEVENT_MGR::UnsubscribeEvent()
* 
*************************************************************/
void dsEVENT_MGR::UnsubscribeEvent(int id, const void *ptr, DWORD userId)
{
   ITEM_REC &rec = items[id];

   int threadId = GetThreadId();

   THREAD_ITEM_INFO &info = rec.threadInfo[threadId];

   int idx = info.idxHandlerChain;
   int idxPrev = -1;
   while (idx >= 0) {
      ITEM_HANDLER &handler = handlers[idx];
      if ((handler.type == ST_POLL && handler.userId == userId && subscribers[handler.idxSubscriber].subscriber == ptr) || 
         (handler.type != ST_POLL && handler.userId == userId && handler.pHandler == ptr)) {
            break;
      }
      idxPrev = idx;
      idx = handler.idxNext;
   }

   if (idx < 0) {
      //ASSERT(FALSE);
      return;
   }

   osLOCKER locker(lock); // we're going to do some unsafe operations

   ITEM_HANDLER &handler = handlers[idx];

   // remove handler from the chain
   if (idxPrev >= 0) {
      handlers[idxPrev].idxNext = handler.idxNext;
   } else {
      info.idxHandlerChain = handler.idxNext;
   }

   if (handler.type == ST_POLL) {
      THREAD_INFO &ti = threadInfo[threadId];

      // mark pending notifications as processed and erase the queue
      SUBSCRIBER_REC &sr = subscribers[handler.idxSubscriber];
      int idxNotif = sr.idxNotifyHead;
      int idxNotifPrev = -1;

      while (idxNotif >= 0) {
         PENDING_ITEM &pi = ti.notifications[idxNotif];
         int next = pi.idxNext;

         if (events[pi.idxEvent].id == id) {
            // it's ok to go without memory barrier
            events[pi.idxEvent].refCounters[threadId] --;
            ASSERT(events[pi.idxEvent].refCounters[threadId] >= 0);

            // remove from the chain
            if (idxNotifPrev >= 0) {
               ti.notifications[idxNotifPrev].idxNext = next;
            } else {
               sr.idxNotifyHead = next;
            }

            ti.notifications.Free(idxNotif); // erase
         } else {
            idxNotifPrev = idxNotif;
         }

         idxNotif = next;
      }
      sr.idxNotifyTail = idxNotifPrev;

      // erase subscriber if there's no more references
      if (--sr.refCnt == 0) {
         subscriberMap.Erase(sr.subscriber);

         //ASSERT(sr.idxNotifyHead < 0 && sr.idxNotifyTail < 0);
         subscribers.Free(handler.idxSubscriber);
      }
   }

   handlers.Free(idx); // erase handler

   return;
}

/*************************************************************
* dsEVENT_MGR::SignalEvent()
* 
*************************************************************/
void dsEVENT_MGR::SignalEvent(int id, const dsPARAM_LIST *params)
{
   int threadId = GetThreadId();
   STRONG_ASSERT(threadId >= 0 && threadId < DS_EVENT_MAX_THREAD);
   THREAD_INFO &ti = threadInfo[threadId];
   STRONG_ASSERT(ti.isActive);

   ITEM_REC &ir = items[id];

   if (ti.idxEventEmpty < 0) {   // no empty elements. need to allocate some
      osLOCKER locker(lock);     // lock event pool, unlock right after allocation is complete

      STRONG_ASSERT(ti.numEmptyElements == 0);

      int idx = -1;
      for (int j = 0; j < DS_EMPTY_CHAIN_LENGTH; j++) {
         int next = idx;
         idx = events.Alloc();
         events[idx].idxNext = next;
      }

      ti.idxEventEmpty     = idx;
      ti.numEmptyElements  = DS_EMPTY_CHAIN_LENGTH;      
   }

   // remove one element from the empty chain and ready it to become head event
   int idxNext = ti.idxEventEmpty;
   EVENT &ev = events[idxNext];

   ti.idxEventEmpty = ev.idxNext; 
   ti.numEmptyElements --;

   ev.idxNext  = -1;
   ev.id       = -1;
   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      ev.refCounters[i] = -1;
   }

   // now let's put event into current event
   EVENT &evCur = events[ti.idxEventHead];
   evCur.id    = id;
   evCur.age   = ++ti.age;
   
   // setup thread mask
   DWORD mask = 0;
   for (int j = 0; j < DS_EVENT_MAX_THREAD; ++j) {
      if (ir.threadInfo[j].idxHandlerChain >= 0) {
         mask |= (1 << j);
      }
   }
   evCur.threadMask = mask;

   if (params) {
      // if there's recipient from diff thread try to convert params to mt-safe analogues
      if (mask & ~(1<<threadId)) {
         // convert unsafe params
         evCur.params.Clear();
         for (int k = 0; k < params->Length(); ++k) {
            const dsPARAM& p = params->operator [](k);
            if (! p.data.IsMTSafe()) {
               dsDATA data = p.data;
               if (! data.MakeMTSafe()) {
                 // couldn't convert to MT safe type
                 STRONG_ASSERT(FALSE);
               }
               evCur.params.Add(p.id, data);
            } else {
               evCur.params.Add(p);
            }
         }
      } else {
         evCur.params   = *params;         
      }
   }

   int idxEventCur = ti.idxEventHead;
   ti.idxEventHead   = idxNext;

   osMemoryBarrier(); // ensure all the data is submitted to memory

   // hit me! 
   evCur.idxNext     = idxNext;  // our empty element becomes the new head element
   // as soon as this operation is submitted to the cache, this event becomes visible to other threads

   if (!ti.isDispatching) {
      DispatchEvents();
      //DispatchEvent(idxEventCur);   // process event in our own thread immediately
   }

   return;
}

/*************************************************************
* dsEVENT_MGR::LockImmediateEvents()
* 
*************************************************************/
void dsEVENT_MGR::LockImmediateEvents(void)
{
   int threadId = GetThreadId();
   THREAD_INFO &ti = threadInfo[threadId];
   STRONG_ASSERT(ti.isActive);

   ti.isDispatching++;

   return;
}

/*************************************************************
* dsEVENT_MGR::UnlockImmediateEvents()
* 
*************************************************************/
void dsEVENT_MGR::UnlockImmediateEvents(void)
{
   int threadId = GetThreadId();
   THREAD_INFO &ti = threadInfo[threadId];
   STRONG_ASSERT(ti.isActive);

   ti.isDispatching--;

   return;
}


/*************************************************************
* *dsEVENT_MGR::PeekEvent()
* 
*************************************************************/
const dsEVENT *dsEVENT_MGR::PeekEvent(int idSubscriber)
{
   int threadId = GetThreadId();
   THREAD_INFO &ti = threadInfo[threadId];

   SUBSCRIBER_REC &sr = subscribers[idSubscriber];
#ifndef _RETAIL
   STRONG_ASSERT(sr.threadId == threadId);
#endif

   if (sr.idxNotifyHead < 0) {
      return NULL;
   }

   PENDING_ITEM &pi = ti.notifications[sr.idxNotifyHead];
   EVENT &ev = events[pi.idxEvent];

   static AP_DECLARE_IN_TLS dsEVENT retEvent;
   retEvent.id          = ev.id;
   retEvent.userId      = pi.userId;
   retEvent.paramList   = ev.params;

   return &retEvent;
}

/*************************************************************
* dsEVENT_MGR::DismissEvent()
* 
*************************************************************/
void dsEVENT_MGR::DismissEvent(int idSubscriber)
{
   int threadId = GetThreadId();
   THREAD_INFO &ti = threadInfo[threadId];

   SUBSCRIBER_REC &sr = subscribers[idSubscriber];
#ifndef _RETAIL
   STRONG_ASSERT(sr.threadId == threadId);
#endif

   if (sr.idxNotifyHead < 0) {
      ASSERT(FALSE); // what's going on? why we're dismissing event when there is no event?
      return;
   }

   int idx = sr.idxNotifyHead;
   PENDING_ITEM &pi = ti.notifications[idx];
   EVENT &ev = events[pi.idxEvent];

   if (ev.refCounters[threadId] == 1) {
      osMemoryBarrier(); // ensure event data has been read from memory before we dismiss the event
   }

   ev.refCounters[threadId] --; // release event

   if (sr.idxNotifyTail == sr.idxNotifyTail) { // no more notifications
      sr.idxNotifyHead  = -1;
      sr.idxNotifyTail  = -1;
   } else {
      sr.idxNotifyHead  = pi.idxNext;
   }

   ti.notifications.Free(idx); // now we can erase notification

   return;
}

/*************************************************************
* dsEVENT_MGR::DispatchEvent()
* 
*************************************************************/
void dsEVENT_MGR::DispatchEvent(int srcThread, int idxEvent)
{
   int threadId = GetThreadId();
   THREAD_INFO &ti = threadInfo[threadId];

   ti.isDispatching++;

   EVENT &ev = events[idxEvent];
   ITEM_REC &ir = items[ev.id];
   int idx = ir.threadInfo[threadId].idxHandlerChain;

   int totalPending = 0;

   while (idx >= 0) {
      ITEM_HANDLER &handler = handlers[idx];

      // check thread mask first, than age
      if (! (ev.threadMask & (1<<threadId)) ||
            (srcThread == threadId && _Diff(ev.age, handler.age) <= 0) ) {
         idx = handler.idxNext;
         continue;
      }

      dsEVENT retEvent;
      retEvent.id          = ev.id;
      retEvent.paramList   = ev.params;
      retEvent.userId      = handler.userId;

#ifndef _RETAIL
      if (srcThread != threadId) {
         // let's check multi-threading type safety
         for (int i = 0; i < ev.params.Length(); i++) {
            STRONG_ASSERT(ev.params[i].data.IsMTSafe());
         }
      }
#endif

      switch (handler.type) {
         case ST_CALLBACK:
            handler.cbHandler(retEvent);
            break;

         case ST_IFACE:
            handler.pHandler->OnEvent(retEvent);
            break;

         case ST_POLL:
            {
               int idxPending = ti.notifications.Alloc();
               SUBSCRIBER_REC &sr = subscribers[handler.idxSubscriber];
               PENDING_ITEM &pi = ti.notifications[idxPending];
               pi.idxEvent = idxEvent;
               pi.idxNext  = sr.idxNotifyTail;
               pi.userId   = handler.userId;
               sr.idxNotifyTail = idxPending;
               if (sr.idxNotifyHead >= 0) {
                  sr.idxNotifyHead = idxPending;
               }
               totalPending ++;
            }
            break;
      }     
      idx = handler.idxNext;
   }

   if (totalPending == 0) {
      osMemoryBarrier(); // ensure that event is ready to be erased
   }

   ev.refCounters[threadId] = totalPending;

   ti.isDispatching--;

   return;
}

/*************************************************************
* dsEVENT_MGR::PurgeEvents()
* 
*************************************************************/
void dsEVENT_MGR::PurgeEvents(void)
{
   int threadId = GetThreadId();
   THREAD_INFO &ti = threadInfo[threadId];

   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      if (!threadInfo[i].isActive) {
         continue; // skip inactive threads
      }

      int idxEvent = ti.idxThreadEvent[i];
      if (idxEvent < 0) {
         idxEvent = threadInfo[i].idxEventTail;

         if (idxEvent < 0) {
            ASSERT(FALSE);
            continue;
         }
      }

      while (TRUE) {
         EVENT &ev = events[idxEvent];
         if (ev.idxNext < 0) {
            break;
         }

         int next = ev.idxNext;

         ev.refCounters[threadId] = 0;

         idxEvent = next;
      }

      ti.idxThreadEvent[i] = idxEvent;
   }

   return;
}


/*************************************************************
* dsEVENT_MGR::DiscardProcessedEvents()
* 
*************************************************************/
void dsEVENT_MGR::DiscardProcessedEvents(void)
{
   // now we discard processed events

   int threadId = GetThreadId();
   THREAD_INFO &ti = threadInfo[threadId];

   while (ti.idxEventTail != ti.idxEventHead) {
      EVENT &ev = events[ti.idxEventTail];

      for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
         if (threadInfo[i].isActive /*&& (ev.threadMask & (1 << i))*/ && (ev.refCounters[i] != 0)) {
            return; // event has not been released yet
         }
      }

      int next = ev.idxNext;

      ev.id = -1;
      ev.params.Clear();
      ev.idxNext  = ti.idxEventEmpty;

      ti.idxEventEmpty = ti.idxEventTail;
      ti.numEmptyElements  ++;

      ti.idxEventTail = next;
   }

   // discard some empty elements if there are too many

   if (ti.numEmptyElements > DS_EMPTY_CHAIN_LENGTH * 2) { 
      osLOCKER locker(lock);

      int num = ti.numEmptyElements - DS_EMPTY_CHAIN_LENGTH;

      for (int i = 0; i < num; i++) {
         int next = events[ti.idxEventEmpty].idxNext;
         events.Free(ti.idxEventEmpty);
         ti.idxEventEmpty = next;
      }

      ti.numEmptyElements = DS_EMPTY_CHAIN_LENGTH;
   }

   return;
}

/*************************************************************
* dsEVENT_MGR::ProcessEvents()
* 
*************************************************************/
void dsEVENT_MGR::ProcessEvents(void)
{
   DispatchEvents();
   DiscardProcessedEvents();
}

/*************************************************************
* dsEVENT_MGR::DispatchEvents()
* 
*************************************************************/
void dsEVENT_MGR::DispatchEvents(void)
{
   int threadId = GetThreadId();
   THREAD_INFO &ti = threadInfo[threadId];

   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      if (!threadInfo[i].isActive) {
         continue; // skip inactive threads
      }

      int idxEvent = ti.idxThreadEvent[i];
      if (idxEvent < 0) {
         idxEvent = threadInfo[i].idxEventTail;

         if (idxEvent < 0) {
            ASSERT(FALSE);
            continue;
         }
      }

      while (TRUE) {
         EVENT &ev = events[idxEvent];
         if (ev.idxNext < 0) {
            break;
         }

         int next = ev.idxNext;

         DispatchEvent(i, idxEvent);

         idxEvent = next;
      }

      ti.idxThreadEvent[i] = idxEvent;
   }

   return;
}


/*************************************************************
* dsSTATE_MGR::dsSTATE_MGR()
* 
*************************************************************/
dsSTATE_MGR::dsSTATE_MGR(void) : threadMask(0), stateMap(AP_CL), stateIndex(AP_CL), states(AP_CL) {
   
   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      THREAD_INFO &ti = threadInfo[i];
      ti.isActive    = FALSE;
   }

   stateMap.Init(1024);

   return;
}

dsSTATE_MGR::~dsSTATE_MGR(void)
{
   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      THREAD_INFO &ti = threadInfo[i];
      if (ti.isActive) {
         ASSERT(FALSE); // some thread was not properly terminated!
         break;
      }
   }

   return;
}

/*************************************************************
* dsSTATE_MGR::InitThread()
* 
*************************************************************/
void dsSTATE_MGR::InitThread(void)
{
   osLOCKER locker(lock);

   BOOL isAlreadyInited = FALSE;
   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      THREAD_INFO &ti = threadInfo[i];

      if (ti.isActive == TRUE && GetThreadId() == i) {
         isAlreadyInited = TRUE;
         break;
      }
   }

   ASSERT(!isAlreadyInited);
   if (isAlreadyInited) {
      return;
   }

   //STATE_MAP::RegisterThread();

   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      THREAD_INFO &ti = threadInfo[i];

      if (ti.isActive == FALSE) {
         GetThreadId() = i;
         ti.safePassMask   = 0;
         
         osMemoryBarrier();

         _SetStateYes(threadMask, i);
         ti.isActive    = TRUE;

         return;
      }
   }


   STRONG_ASSERT(FALSE); // too many thread registered

   return;
}

/*************************************************************
* dsSTATE_MGR::TermThread()
* 
*************************************************************/
void dsSTATE_MGR::TermThread(void)
{
   //STATE_MAP::UnregisterThread();

   int threadId = GetThreadId();
   STRONG_ASSERT(threadId >= 0 && threadId < DS_EVENT_MAX_THREAD);
   THREAD_INFO &ti = threadInfo[threadId];

   // let's erase all states belonging to our thread
   {
      STATE_MAP::const_iterator it = stateMap.Begin();
      while (!it.isEnd()) {
         STATE_REC &sr = stateIndex[it.Get()];

         int storageId = sr.idxThreadData[threadId];
         if (storageId >= 0) {
            if (sr.idxData == storageId) {
               sr.idxData = -1; // erase value
            }
         }

         it++;
      }
   }

   osMemoryBarrier();

   // warning! this loop is a potential deadlock in some simultaneous thread deinitialization scenarios.
   // like we stuck here and some parallel thread is also stuck in a similar loop belonging
   // to another system. if this happens, we'll need to unroll this loop and include some other system
   // processing, which will allows one of the systems to finish deinitialization
   while (!_IsEachState(ti.safePassMask, threadMask & ~(1 << threadId))) {
      osSleep(5);
      UpdateThread();
   }

   osLOCKER locker(lock);

   // now we can delete storage items belonging to our thread
   STATE_MAP::const_iterator it = stateMap.Begin();
   while (!it.isEnd()) {
      STATE_REC &sr = stateIndex[it.Get()];

      int storageId = sr.idxThreadData[threadId];
      if (storageId >= 0) {
         states.Free(storageId);
      }

      it++;
   }

   _SetStateNo(threadMask, threadId);

   osMemoryBarrier(); // let's ensure all data is submitted to memory before we...

   ti.isActive = FALSE; // ...mark thread as not active, so other threads may allocate it
   GetThreadId() = -1;

   return;
}

/*************************************************************
* dsSTATE_MGR::UpdateThread()
* 
*************************************************************/
void dsSTATE_MGR::UpdateThread(void)
{
   // Ignore possible updates from unregistered threads
   if (GetThreadId() == -1) {
      return;
   }

   STRONG_ASSERT(threadInfo[GetThreadId()].isActive);
   //STATE_MAP::UpdateThread();
   
   osLOCKER locker(lock); // safe mark
   
   int threadId = GetThreadId();

   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      THREAD_INFO &ti = threadInfo[i];
      _SetStateYes(ti.safePassMask, 1 << threadId);
   }

   return;
}

/*************************************************************
* dsSTATE_MGR::RegisterState()
* 
*************************************************************/
int dsSTATE_MGR::RegisterState(dsSTRID id, bool isToggleEventOnChange)
{
   STRONG_ASSERT(GetThreadId() >= 0 && threadInfo[GetThreadId()].isActive);

   const int *item = stateMap.Find(id.CStr());
   if (item != NULL) {
      //ASSERT((stateIndex[*item].eventId >= 0) == isToggleEventOnChange); // registered with different isToggleEventOnChange
      return *item;
   }

   osLOCKER locker(lock);

   item = stateMap.Find(id.CStr()); // search once again to ensure no one added this item while we were acquiring the lock
   if (item != NULL) {
      //ASSERT((stateIndex[*item].eventId >= 0) == isToggleEventOnChange); // registered with different isToggleEventOnChange
      return *item;
   }

   int newItemId = stateIndex.Alloc();
   STATE_REC &rec = stateIndex[newItemId];
   rec.idxData = -1;
   if (isToggleEventOnChange) {
      char s[256];
      sprintf(s, "On%sChanged", id.CStr());
      rec.eventId = gEventMgr->RegisterEvent(s);
   } else {
      rec.eventId = -1;
   }
   for (int i = 0; i < DS_EVENT_MAX_THREAD; i++) {
      rec.idxThreadData[i] = -1;
   }

   stateMap.Insert(dsSTRID(id).CStr(), newItemId); // memory barrier is inside, so the data is immediately available for other threads

   return newItemId;
}

/*************************************************************
* dsSTATE_MGR::SetState()
* 
*************************************************************/
void dsSTATE_MGR::SetState(int id, const dsDATA &data)
{
   STRONG_ASSERT(GetThreadId() >= 0 && threadInfo[GetThreadId()].isActive);

   int threadId = GetThreadId();

   STATE_REC &stateRec = stateIndex[id];
   if (stateRec.idxThreadData[threadId] < 0) {
      osLOCKER locker(lock);

      stateRec.idxThreadData[threadId] = states.Alloc();
   }

   dsDATA &stateData = states[stateRec.idxThreadData[threadId]];
   stateData = data;

   osMemoryBarrier(); // submit data to the cache

   stateRec.idxData  = stateRec.idxThreadData[threadId]; // change the state
   
   if (stateRec.eventId >= 0) {
      dsSTATIC_PARAM_LIST<1> params;
      params.Add(valId, data); 
      gEventMgr->SignalEvent(stateRec.eventId, &params);
   }

   return;
}

/*************************************************************
* dsSTATE_MGR::EraseState()
* 
*************************************************************/
void dsSTATE_MGR::EraseState(int id)
{
   STRONG_ASSERT(GetThreadId() >= 0 && threadInfo[GetThreadId()].isActive);

   STATE_REC &stateRec = stateIndex[id];
   stateRec.idxData  = -1;

   return;
}

/*************************************************************
* &dsSTATE_MGR::GetState()
* 
*************************************************************/
const dsDATA &dsSTATE_MGR::GetState(int id)
{
   STATE_REC &stateRec = stateIndex[id];
   if (stateRec.idxData < 0) {
      static dsDATA nullData;
      return nullData;
   }

   return states[stateRec.idxData];
}
